
#include "sipcall.h"
#include <cassert>
#include <cstring>
#include <cstdlib>
#include <fstream>
#include <sstream>

#include "eXosip2/eXosip.h"

#include "rutil/BaseException.hxx"
#include "zklog/zklog_wrapper.h"
#include "bfcp.h"

using namespace std;
using namespace resip;

osip_message_t* resetContact(osip_message_t* msg,
    const ConfigParam& param,
    const std::string& received,
    int rport)
{
    if (!param.mAutoMasqueradeContact)
    {
        osip_contact_t* contact;
        int ret = osip_message_get_contact(msg, 0, &contact);
        if (contact)
        {
            if (!param.mContactString.empty())
            {
                osip_list_remove(&msg->contacts, 0);
                osip_message_set_contact(msg, osip_strdup(param.mContactString.c_str()));
            }
//             else
//             {
//                 osip_uri_set_host(contact->url, osip_strdup(received.c_str()));
//                 osip_uri_set_port(contact->url, osip_strdup(std::to_string(rport).c_str()));
//             }
        }
    }
    return msg;
}

SipCall::SipCall(eXosip_t* ctx, const ConfigParam& param)
    : mParam(param)
    , mSipCtx(ctx)
    , mCallState(CALL_STATUS_IDLE)
    , mDID(0)
    , mCID(0)
    , mNatPort(0)
    , mBfcpClient(0)
{
}

SipCall::~SipCall()
{
}

void SipCall::updateNatInfo(const std::string& nataddress, int natport)
{
    mNatAddress = nataddress;
    mNatPort = natport;
}

bool SipCall::sendOffer(const std::string& targetid, const std::string& sdp)
{
    if (isIncall())
    {
        ZKLOG_WARNING_FMT("There is a call in processing...");
        return false;
    }

    //modify call request line
    ExosipAutoLock locker(mSipCtx);
    osip_message_t* invite;
    int ret = eXosip_call_build_initial_invite(mSipCtx, &invite, mParam.get_to(targetid).c_str(), mParam.get_from().c_str(), mParam.get_route().c_str(), "test call");
    ret = osip_message_set_content_type(invite, "application/sdp");
    ret = osip_message_set_body(invite, sdp.c_str(), sdp.size());

    resetContact(invite, mParam, mNatAddress, mNatPort);

    mCID = eXosip_call_send_initial_invite(mSipCtx, invite);

    transitionState(CALL_STATUS_CALLING);

    ZKLOG_INFO_FMT("==> [INVITE] request cid={} to target:{}, sdp=((ENABLE DEBUG LOG FOR DETAILS))", mCID, targetid);
    ZKLOG_DEBUG_FMT("cid={}, offer sdp=\n{}", mCID, sdp);

    return true;
}

bool SipCall::sendAck(int tid)
{
    int ret = OSIP_SUCCESS;
    osip_message_t* ack;
    ret = eXosip_call_build_ack(mSipCtx, tid, &ack);
    ret = eXosip_call_send_ack(mSipCtx, tid, ack);
    ZKLOG_INFO_FMT("==> [ACK] acknowledge. tid={} ", tid);
    return true;
}

void SipCall::onOffer(const eXosip_event* const osipEvt)
{
    mDID = osipEvt->did;
    mCID = osipEvt->cid;
    transitionState(CALL_STATUS_INCOMING);

    ZKLOG_INFO_FMT("<== [INVITE] Incoming call. did={}, cid={}, tid={}, sdp=((ENABLE DEBUG LOG FOR DETAILS))",
        osipEvt->did, osipEvt->cid, osipEvt->tid);

    sdp_message_t* income = eXosip_get_sdp_info(osipEvt->request);
    common::ostring income_body;
    sdp_message_to_str(income, &income_body);
    sdp_message_free(income);
    ZKLOG_DEBUG_FMT("did={}, cid={}, tid={}, onoffer sdp=\n{}", osipEvt->did, osipEvt->cid, osipEvt->tid, income_body);

    sendAnswer(osipEvt);
}

void SipCall::on100(const eXosip_event* const osipEvt)
{
    ZKLOG_INFO_FMT("<== [{} {}] INVITE Processing... did={}, cid={}, tid={}",
        osipEvt->response->status_code, osip_message_get_reason(osipEvt->response->status_code),
        osipEvt->did, osipEvt->cid, osipEvt->tid);
    transitionState(CALL_STATUS_100Tring);
}

void SipCall::on180(const eXosip_event* const osipEvt)
{
    ZKLOG_INFO_FMT("<== [{} {}] INVITE Ringing... did={}, cid={}, tid={}",
        osipEvt->response->status_code, osip_message_get_reason(osipEvt->response->status_code),
        osipEvt->did, osipEvt->cid, osipEvt->tid);
    transitionState(CALL_STATUS_180Ring);
}

void SipCall::onAnswer(const eXosip_event* const osipEvt)
{
    transitionState(CALL_STATUS_200OK);

    sdp_message_t* answer = eXosip_get_sdp_info(osipEvt->response);
    common::ostring answer_body;
    sdp_message_to_str(answer, &answer_body);
    sdp_message_free(answer);

    ZKLOG_INFO_FMT("<== [{} {}] INVITE answer. did={}, cid={}, tid={}, sdp=((ENABLE DEBUG LOG FOR DETAILS))",
        osipEvt->response->status_code, osip_message_get_reason(osipEvt->response->status_code),
        osipEvt->did, osipEvt->cid, osipEvt->tid);
    ZKLOG_DEBUG_FMT("did={}, cid={}, tid={}, onoffer sdp=\n{}",
        osipEvt->did, osipEvt->cid, osipEvt->tid, answer_body);

    mDID = osipEvt->did;
    mCID = osipEvt->cid;
    sendAck(osipEvt->tid);

    transitionState(CALL_STATUS_INCALL);
}

void SipCall::onFail(const eXosip_event* const osipEvt)
{
    ZKLOG_INFO_FMT("<== [{} {}] INVITE failure. did={}, cid={}, tid={}",
        osipEvt->response->status_code, osip_message_get_reason(osipEvt->response->status_code),
        osipEvt->did, osipEvt->cid, osipEvt->tid);
    sendAck(osipEvt->tid);
    transitionState(CALL_STATUS_FAIL);
}

void SipCall::onAck(const eXosip_event* const osipEvt)
{
    ZKLOG_INFO_FMT("<== [ACK] acknowledge. did={}, cid={}, tid={}", osipEvt->did, osipEvt->cid, osipEvt->tid);
    transitionState(CALL_STATUS_INCALL);
}

void SipCall::onCancel(const eXosip_event* const osipEvt)
{
    ZKLOG_INFO_FMT("<== [CANCEL] INVITE cancel. did={}, cid={}, tid={}", osipEvt->did, osipEvt->cid, osipEvt->tid);
    transitionState(CALL_STATUS_COMPLETE);
}

void SipCall::onBye(const eXosip_event* const osipEvt)
{
    ZKLOG_INFO_FMT("<== [BYE] INVITE bye. did={}, cid={}, tid={}", osipEvt->did, osipEvt->cid, osipEvt->tid);
    transitionState(CALL_STATUS_COMPLETE);
    if (mBfcpClient)
    {
        delete mBfcpClient;
        mBfcpClient = 0;
    }
}

void SipCall::releaseCall(const eXosip_event* const osipEvt)
{
    ZKLOG_INFO_FMT("Call released. did={}, cid={}, tid={}", osipEvt->did, osipEvt->cid, osipEvt->tid);
    if (osipEvt->cid == mCID)
    {
        transitionState(CALL_STATUS_IDLE);
    }
}

void SipCall::terminate(const std::string& reason)
{
    if (isIncall())
    {
        int ret = eXosip_call_terminate_with_reason(mSipCtx, mCID, mDID, reason.empty() ? NULL : reason.c_str());
        ZKLOG_INFO_FMT("==> [CANCEL/BYE/602] terminate call. reason={}, did={}, cid={}", reason, mDID, mCID);

        transitionState(CALL_STATUS_TERMINATE);
        if (mBfcpClient)
        {
            delete mBfcpClient;
            mBfcpClient = 0;
        }
    }
}

bool SipCall::isConnected() const
{
    return (mCallState == CALL_STATUS_INCALL);
}

bool SipCall::isIncall() const
{
    return (mCallState >= CALL_STATUS_CALLING && mCallState <= CALL_STATUS_INCALL);
}

std::string SipCall::toString(State state)
{
    switch (state)
    {
    case CALL_STATUS_IDLE:
        return "idle";
    case CALL_STATUS_CALLING:
        return "calling...";
    case  CALL_STATUS_INCOMING:
        return "incoming...";
    case CALL_STATUS_100Tring:
        return "100-Trying...";
    case CALL_STATUS_180Ring:
        return "180-Ringing...";
    case CALL_STATUS_200OK:
        return "200-OK";
    case CALL_STATUS_ANSWERED:
        return "answered";
    case CALL_STATUS_FAIL:
        return "failed";
    case CALL_STATUS_INCALL:
        return "in-call";
    case CALL_STATUS_TERMINATE:
        return "terminate...";
    case CALL_STATUS_COMPLETE:
        return "complete";
    }
    assert(false);
    return "Undefined";
}

void SipCall::transitionState(SipCall::State s)
{
    ZKLOG_DEBUG_FMT("Invite stat changed: {} --> {}", toString(mCallState), toString(s));
    mCallState = s;
}

bool SipCall::sendCallRequest(const std::string& method, const std::string& contenttype, const std::string& msg, void* appdata /*= NULL*/)
{
    int ret = OSIP_SUCCESS;
    osip_message_t* request = NULL;

    ExosipAutoLock locker(mSipCtx);
    ret = eXosip_call_build_request(mSipCtx, mDID, method.c_str(), &request);
    if (OSIP_SUCCESS != ret)
    {
        ZKLOG_ERROR_FMT("eXosip_call_build_request failed with did={}, err={}", mDID, osip_strerror(ret));
        return false;
    }
    if (msg.size() > 0)
    {
        ret = osip_message_set_content_type(request, contenttype.c_str());
        ret = osip_message_set_body(request, msg.c_str(), msg.size());
        request->application_data = appdata;
    }

    //revise contact
    resetContact(request, mParam, mNatAddress, mNatPort);

    ret = eXosip_call_send_request(mSipCtx, mDID, request);
    if (OSIP_SUCCESS == ret)
    {
        ZKLOG_INFO_FMT("==> [{}] in-dialog {} by={} with content type={}, content body=((ENABLE DEBUG LOG FOR DETAILS))",
            method, method, (int)(intptr_t)appdata, contenttype);
        ZKLOG_DEBUG_FMT(msg);
        return true;
    }
    else
    {
        ZKLOG_ERROR_FMT("eXosip_call_send_request failed with did={}, err={}", mDID, osip_strerror(ret));
        return false;
    }
}

bool SipCall::sendCallResponse(int did, int cid, int tid, int statuscode, const std::string& contenttype, const std::string& msg)
{
    int ret = OSIP_SUCCESS;
    osip_message_t* answer = NULL;

    ExosipAutoLock locker(mSipCtx);
    ret = eXosip_call_build_answer(mSipCtx, tid, statuscode, &answer);
    if (OSIP_SUCCESS != ret)
    {
        ZKLOG_ERROR_FMT("Failed to build call answer. did={}, cid={}, tid={}, err={}", did, cid, tid, osip_strerror(ret));
        return false;
    }
    if (msg.size() > 0)
    {
        ret = osip_message_set_content_type(answer, contenttype.c_str());
        ret = osip_message_set_body(answer, msg.c_str(), msg.size());
    }

    //revise contact
    resetContact(answer, mParam, mNatAddress, mNatPort);

    ret = eXosip_call_send_answer(mSipCtx, tid, statuscode, answer);
    if (OSIP_SUCCESS == ret)
    {
        ZKLOG_INFO_FMT("==> [200OK] INVITE answer. did={}, cid={}, tid={}, statuscode={}, content type={}, content length={}, content body=((ENABLE DEBUG LOG FOR DETAILS))",
            did, cid, tid, statuscode, contenttype, msg.size());
        ZKLOG_DEBUG_FMT("did={}, cid={}, tid={}, answer sdp=\n{}", did, cid, tid, msg);
        return true;
    }
    else
    {
        ZKLOG_ERROR_FMT("Failed to send call answer to did={}, cid={}, tid={}, err={}", did, cid, tid, osip_strerror(ret));
        return false;
    }
}

bool SipCall::sendAnswer(const eXosip_event* const osipEvt)
{
    UInt32 confid = 0;
    UInt16 userid = 0;

    sdp_connection_t* svr_conn = 0;
    int bfcpServerPort = 0;

    // find BFCP media
    sdp_message_t* income_sdp = eXosip_get_sdp_info(osipEvt->request);
    int pos = 0;
    sdp_media_t *med = (sdp_media_t *)osip_list_get(&income_sdp->m_medias, pos);
    while (med != NULL) {
        if (med->m_media != NULL &&
            osip_strcasecmp(med->m_media, "application") == 0 &&
            osip_strcasecmp(med->m_proto, "UDP/BFCP") == 0)
        {
            svr_conn = (sdp_connection_t*)osip_list_get(&med->c_connections, 0);
            if (!svr_conn)
            {
                svr_conn = income_sdp->c_connection;
            }
            bfcpServerPort = std::stoul(med->m_port);
            break;
        }

        ++pos;
        med = (sdp_media_t *)osip_list_get(&income_sdp->m_medias, pos);
    }
    // find bfcp confid and userid
    if (med)
    {
        pos = 0;
        sdp_attribute_t* attr = (sdp_attribute_t*)osip_list_get(&med->a_attributes, pos);
        while (attr) {
            if (osip_strcasecmp(attr->a_att_field, "confid") == 0) {
                confid = std::stoul(attr->a_att_value);
            }
            else if (osip_strcasecmp(attr->a_att_field, "userid") == 0) {
                userid = std::stoul(attr->a_att_value);
            }
            ++pos;
            attr = (sdp_attribute_t*)osip_list_get(&med->a_attributes, pos);
        }

        if (confid == 0 || userid == 0) {
            ZKLOG_WARNING_FMT("invalid BFCP confid or userid");
            terminate("Internal error: invalid BFCP confid or userid");
            return false;
        }
    }

    ifstream ifs("sdp-answer.txt");
    if (!ifs)
    {
        ZKLOG_WARNING_FMT("Not exist sdp_answer.txt");
        terminate("Internal error: invalid answer sdp");
        return false;
    }

    stringstream ss;
    ss << ifs.rdbuf();
    string sdp(ss.str());
    ifs.close();

    sdp_message_t* answer_sdp = 0;
    int res = sdp_message_init(&answer_sdp);
    res = sdp_message_parse(answer_sdp, sdp.c_str());
    sdp_connection_t* local_conn = 0;
    int bfcpLocalPort = 0;
    // find local BFCP media port
    pos = 0;
    med = (sdp_media_t *)osip_list_get(&answer_sdp->m_medias, pos);
    while (med != NULL) {
        if (med->m_media != NULL &&
            osip_strcasecmp(med->m_media, "application") == 0 &&
            osip_strcasecmp(med->m_proto, "UDP/BFCP") == 0)
        {
            local_conn = (sdp_connection_t*)osip_list_get(&med->c_connections, 0);
            if (!local_conn)
            {
                local_conn = answer_sdp->c_connection;
            }
            bfcpLocalPort = std::stoi(med->m_port);
            break;
        }

        ++pos;
        med = (sdp_media_t *)osip_list_get(&answer_sdp->m_medias, pos);
    }

    if (!svr_conn || !local_conn)
    {
        ZKLOG_WARNING_FMT("server bfcp connection or local bfcp connection is null!");
        terminate("Internal error: invalid bfcp connection");
        return false;
    }
    ZKLOG_DEBUG_FMT("remote BFCP: addr={}, port={}, userid={}, confid={}", svr_conn->c_addr, bfcpServerPort, confid, userid);
    ZKLOG_DEBUG_FMT("local BFCP: addr={}, port={}", local_conn->c_addr, bfcpLocalPort);

    // create bfcp
    mBfcpClient = new BFCPClient(confid, userid, svr_conn->c_addr, bfcpServerPort);
    try
    {
        mBfcpClient->addTransport(UDP, V4, Data::Empty, bfcpLocalPort);
    }
    catch (resip::BaseException& e)
    {
        ZKLOG_ERROR_FMT("Failed to addTransport for bfcp");
        terminate("Internal error: Failed to addTransport for bfcp");
        return false;
    }

    // response 200OK with answer sdp
    bool ret = sendCallResponse(osipEvt->did, osipEvt->cid, osipEvt->tid, 200, "application/sdp", sdp);
    if (ret)
    {
        mBfcpClient->run();
        transitionState(CALL_STATUS_ANSWERED);
    }
    else
    {
        terminate("Internal error: send answer");
    }

    return ret;
}



